package br.ufrgs.dbproject.idgenerator.numberingscheme;

import br.ufrgs.dbproject.idgenerator.ID;

public class ExtendedNumberingSchemeID implements ID {

	public static final int ROOT_NODE_DEEP = 0;
	public static final long ROOT_NODE_ORDER = 1L;
	public static final long DEFAULT_UNIQUE_ID = 1L;
	public static final long DEFAULT_NODE_SIZE = 100L;
	public static final long MAXIMUM_NODE_SIZE = 999999999L;
	
	private final Long uniqueID;

	private final Integer deep;
	private final Long order;
	private Long size;
	
	
	private ExtendedNumberingSchemeID parentID;
	private ExtendedNumberingSchemeID lastCreatedChild;
	
	public ExtendedNumberingSchemeID(Long uniqueID, Long order, Integer deep) {
		
		this.uniqueID = uniqueID;
		this.order = order;
		this.deep = deep;
		this.size = DEFAULT_NODE_SIZE;
	}

	public static ExtendedNumberingSchemeID createEmptyID() {
		return new ExtendedNumberingSchemeID(null, null, null);
	}

	public static ExtendedNumberingSchemeID createRootID() {
		return new ExtendedNumberingSchemeID(DEFAULT_UNIQUE_ID, ROOT_NODE_ORDER, ROOT_NODE_DEEP);
	}
	
	public ExtendedNumberingSchemeID createChild() {

		if (this.isEmpty()) {
			return ExtendedNumberingSchemeID.createRootID();
		}
		
		Long childOrder;
		Long childUniqueID = uniqueID + 1;
		Integer childDeep = deep + 1;

		
		if (lastCreatedChild != null) {
			childOrder = lastCreatedChild.getOrder() + lastCreatedChild.getSize();
		} else {
			childOrder = this.order + 1;
		}
		
		ExtendedNumberingSchemeID newChild = new ExtendedNumberingSchemeID(childUniqueID, childOrder, childDeep);
		newChild.setParentID(this);
		newChild.updateParentIDSize();
		
		this.lastCreatedChild = newChild;
		
		return newChild;
	}
	
	public boolean hasParent() {
		return parentID != null;
	}
	
	public boolean isParentOf(ExtendedNumberingSchemeID child) {
		return child.hasParent() && this.uniqueID.equals(child.getParentID().getUniqueID());
	}
	
	public boolean isChildOf(ExtendedNumberingSchemeID id) {
		return this.hasParent() && this.parentID.getUniqueID().equals(id.getUniqueID());
	}
	
	public boolean isDescendantOf(ExtendedNumberingSchemeID id) {
		return this.order > id.getOrder() &&
				(this.order + this.size) <= (id.getOrder() + id.getSize());
	}
	
	public boolean isEmpty() {
		return uniqueID == null || order == null || deep == null;
	}
	
	public void increaseSize(Long amount) {
		this.size += amount;
	}
	
	public void updateParentIDSize() {
		this.parentID.increaseSize(this.size);
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((deep == null) ? 0 : deep.hashCode());
		result = prime * result + ((lastCreatedChild == null) ? 0 : lastCreatedChild.hashCode());
		result = prime * result + ((order == null) ? 0 : order.hashCode());
		result = prime * result + ((parentID == null) ? 0 : parentID.hashCode());
		result = prime * result + ((size == null) ? 0 : size.hashCode());
		result = prime * result + ((uniqueID == null) ? 0 : uniqueID.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ExtendedNumberingSchemeID other = (ExtendedNumberingSchemeID) obj;
		if (deep == null) {
			if (other.deep != null)
				return false;
		} else if (!deep.equals(other.deep))
			return false;
		if (lastCreatedChild == null) {
			if (other.lastCreatedChild != null)
				return false;
		} else if (!lastCreatedChild.equals(other.lastCreatedChild))
			return false;
		if (order == null) {
			if (other.order != null)
				return false;
		} else if (!order.equals(other.order))
			return false;
		if (parentID == null) {
			if (other.parentID != null)
				return false;
		} else if (!parentID.equals(other.parentID))
			return false;
		if (size == null) {
			if (other.size != null)
				return false;
		} else if (!size.equals(other.size))
			return false;
		if (uniqueID == null) {
			if (other.uniqueID != null)
				return false;
		} else if (!uniqueID.equals(other.uniqueID))
			return false;
		return true;
	}

	public Long getOrder() {
		return order;
	}
	
	public Long getSize() {
		return size;
	}

	public Long getUniqueID() {
		return uniqueID;
	}
	
	public Integer getDeep() {
		return deep;
	}

	public ExtendedNumberingSchemeID getParentID() {
		return parentID;
	}

	public void setParentID(ExtendedNumberingSchemeID parentID) {
		this.parentID = parentID;
	}
}
